﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.DirectoryServices;
using System.Diagnostics;
using System.Management;
using System.ServiceProcess;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.Common;

namespace Microsoft.Vsts.Ranger.Migration.TfsToTfs.Client.Common
{
    class ServiceOwnerValidator
    {
        public class ServiceNotExistException : InvalidOperationException
        {
        }

        public class ServiceNotRunningException : InvalidOperationException
        {
        }

        const string CPATHTOCOMPUTER = @"WinNT://";

        /// <summary>
        /// Check if a Windows Service is run by an administrator
        /// </summary>
        /// <param name="serviceName">name of the service</param>
        /// <returns>null if we cannot determine; true if the service is run by administrator, false otherwise</returns>
        public static bool? IsServiceOwnerAnAdmin(string serviceName)
        {
            if (string.IsNullOrEmpty(serviceName))
            {
                throw new ArgumentNullException("serviceName");
            }

            bool? serviceRunning = IsServiceRunning(serviceName);
            if (!serviceRunning.HasValue)
            {
                throw new ServiceNotExistException();
            }
            else if (!serviceRunning.Value)
            {
                throw new ServiceNotRunningException();
            }

            string ownerAccount = GetServiceOwner(serviceName);

            if (string.IsNullOrEmpty(ownerAccount))
            {
                return null;
            }

            return IsUserInAdminGroup(ownerAccount);
        }

        private static bool? IsServiceRunning(string serviceName)
        {
            ServiceController[] serviceControllers = ServiceController.GetServices();
            foreach (ServiceController ctrlr in serviceControllers)
            {
                if (ctrlr.ServiceName.Equals(serviceName, StringComparison.OrdinalIgnoreCase))
                {
                    if (ctrlr.Status == ServiceControllerStatus.Running)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return null;
        }

        private static IEnumerable GetAdminGroupUsers()
        {
            DirectoryEntry localMachine = new DirectoryEntry(CPATHTOCOMPUTER + Environment.MachineName);
            DirectoryEntry adminGroup = localMachine.Children.Find("administrators", "group");

            object members = adminGroup.Invoke("members", null);
            return (IEnumerable)members;
        }

        private static bool IsUserInAdminGroup(string username)
        {
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            foreach (object groupMember in GetAdminGroupUsers())
            {
                DirectoryEntry member = new DirectoryEntry(groupMember);
                string userPath = CPATHTOCOMPUTER + username.Replace(@"\", "/");
                if (member.Path.Equals(userPath, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        private static string GetServiceOwner(string serviceName)
        {
            string filter =
                String.Format("SELECT * FROM Win32_Service WHERE Name=\"{0}\"", serviceName);

            ManagementObjectSearcher query = new ManagementObjectSearcher(filter);

            // No match = stopped
            if (query == null)
            {
                return string.Empty;
            }

            ManagementObjectCollection services = query.Get();

            foreach (ManagementObject service in services)
            {
                var oConn = new ConnectionOptions();
                oConn.EnablePrivileges = true;
                var oMs = new ManagementScope(@"\\localhost", oConn);

                var oQuery = new System.Management.ObjectQuery(string.Format(
                    "select * from Win32_Process where ProcessId=\"{0}\"",
                    service["ProcessId"].ToString()));
                var oSearcher = new ManagementObjectSearcher(oMs, oQuery);
                var oReturnCollection = oSearcher.Get();

                foreach (ManagementObject oReturn in oReturnCollection)
                {
                    const int USERNAME_INDEX = 0;
                    const int DOMAIN_INDEX = 1;
                    string[] owner = new String[2];
                    oReturn.InvokeMethod("GetOwner", (object[])owner);

                    if (!string.IsNullOrEmpty(owner[USERNAME_INDEX])
                        && !string.IsNullOrEmpty(owner[DOMAIN_INDEX]))
                    {
                        return owner[DOMAIN_INDEX] + @"\" + owner[USERNAME_INDEX];
                    }

                    break;
                }

                break;
            }
            return string.Empty;
        }
    }
}
